import React, { useState, useCallback, useEffect, memo } from 'react';

/**
 * useCallback Hook示例组件
 * 
 * 这个组件展示了React useCallback Hook的使用场景：
 * 1. 记忆回调函数以避免不必要的子组件重新渲染
 * 2. 在依赖项变化时才创建新的回调函数
 * 3. 与React.memo结合使用优化性能
 * 4. 避免useEffect中的无限循环
 */

// 使用memo创建一个只在props变化时才重新渲染的组件
const Button = memo(function Button({ onClick, children }) {
  console.log(`${children} 按钮渲染`);
  
  // 使用useEffect来显示组件重新渲染的次数
  const [renderCount, setRenderCount] = useState(0);
  
  useEffect(() => {
    setRenderCount(prevCount => prevCount + 1);
  }, []);
  
  return (
    <div className="memoized-button">
      <button onClick={onClick}>{children}</button>
      <p className="render-info">渲染次数: {renderCount}</p>
    </div>
  );
});

// 使用memo创建一个显示计数的组件
const Count = memo(function Count({ value, label }) {
  console.log(`${label} 计数组件渲染`);
  
  // 使用useEffect来显示组件重新渲染的次数
  const [renderCount, setRenderCount] = useState(0);
  
  useEffect(() => {
    setRenderCount(prevCount => prevCount + 1);
  }, []);
  
  return (
    <div className="count-display">
      <p>{label}: {value}</p>
      <p className="render-info">渲染次数: {renderCount}</p>
    </div>
  );
});

function UseCallbackExample() {
  // 状态
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [otherState, setOtherState] = useState(0);
  
  // 示例1: 不使用useCallback - 每次渲染都会创建新函数
  const incrementWithoutCallback = () => {
    setCount1(count => count + 1);
  };
  
  // 示例2: 使用useCallback - 只有在依赖项变化时才创建新函数
  const incrementWithCallback = useCallback(() => {
    setCount2(count => count + 1);
  }, []); // 空依赖数组意味着这个函数只会被创建一次
  
  // 示例3: 带依赖项的useCallback
  const incrementBothCounts = useCallback(() => {
    setCount1(c => c + 1);
    setCount2(c => c + 1);
  }, []); // 空依赖数组，函数只创建一次
  
  // 示例4: 依赖于状态的useCallback
  const resetCounts = useCallback(() => {
    setCount1(0);
    setCount2(0);
  }, []); // 空依赖数组，函数只创建一次
  
  // 示例5: 在useEffect中使用的回调
  const [data, setData] = useState(null);
  
  // 使用useCallback记忆fetchData函数，避免useEffect的无限循环
  const fetchData = useCallback(() => {
    console.log('获取数据...');
    // 模拟API调用
    setTimeout(() => {
      setData(new Date().toLocaleTimeString());
    }, 1000);
  }, []); // 空依赖数组，函数只创建一次
  
  // 使用fetchData函数的useEffect
  useEffect(() => {
    fetchData();
  }, [fetchData]); // 依赖于fetchData，但因为使用了useCallback，不会导致无限循环
  
  // 触发重新渲染但不影响计数器的函数
  const triggerRerender = () => {
    setOtherState(s => s + 1);
  };
  
  return (
    <div className="hook-example">
      <h2>useCallback Hook 示例</h2>
      
      <section>
        <h3>重新渲染测试</h3>
        <p>其他状态值 (用于触发重新渲染): {otherState}</p>
        <button onClick={triggerRerender}>触发重新渲染</button>
        <p className="note">
          点击此按钮会更新父组件状态，导致重新渲染。
          观察下面的按钮组件的渲染次数来比较使用和不使用useCallback的区别。
        </p>
      </section>
      
      <div className="callback-comparison">
        <section className="without-callback">
          <h3>不使用 useCallback</h3>
          <Count value={count1} label="计数1" />
          <Button onClick={incrementWithoutCallback}>
            增加计数1
          </Button>
          <p className="explanation">
            这个按钮使用普通函数作为onClick处理程序。
            每次父组件重新渲染时，都会创建一个新函数，
            导致Button组件即使使用了memo也会重新渲染。
          </p>
        </section>
        
        <section className="with-callback">
          <h3>使用 useCallback</h3>
          <Count value={count2} label="计数2" />
          <Button onClick={incrementWithCallback}>
            增加计数2
          </Button>
          <p className="explanation">
            这个按钮使用useCallback记忆的函数作为onClick处理程序。
            函数引用在重新渲染之间保持稳定，
            因此Button组件使用memo后不会不必要地重新渲染。
          </p>
        </section>
      </div>
      
      <section>
        <h3>共享回调</h3>
        <Button onClick={incrementBothCounts}>
          同时增加两个计数
        </Button>
        <Button onClick={resetCounts}>
          重置所有计数
        </Button>
      </section>
      
      <section>
        <h3>在useEffect中使用useCallback</h3>
        <p>获取的数据: {data || '加载中...'}</p>
        <Button onClick={fetchData}>
          刷新数据
        </Button>
        <p className="explanation">
          fetchData函数使用useCallback记忆，
          这样它就不会在每次渲染时重新创建，
          避免了useEffect的无限循环。
        </p>
      </section>
      
      <div className="explanation">
        <h3>useCallback Hook 说明:</h3>
        <ul>
          <li><code>const memoizedCallback = useCallback(fn, dependencies)</code> - 记忆一个回调函数</li>
          <li>只有当依赖项数组中的值变化时，才会返回一个新的函数引用</li>
          <li>主要用途是优化性能，特别是当将回调传递给使用React.memo或shouldComponentUpdate优化的子组件时</li>
          <li>避免在useEffect依赖项中使用的函数导致的无限循环</li>
          <li>空依赖数组 <code>[]</code> 意味着回调函数只在组件挂载时创建一次</li>
          <li>不要过度使用 - 只在性能确实受到影响时才需要</li>
        </ul>
      </div>
    </div>
  );
}

export default UseCallbackExample;